const std = @import("std");
const assert = std.debug.assert;
const allocator = std.heap.page_allocator;
const os = @import("../os.zig");
const Page = os.mem.Page;
const fence = os.Lock.fence;

var reg: os.lib.Mmio(e1000, false) = .empty;

var tx_lock: os.Lock.Spin = .init("e1000.tx");
var tx_ring: [16]Desc.Tx align(16) = @splat(.{ .status = .{ .dd = true } });
var tx_bufs: [16]?[]const u8 = @splat(null);

var rx_lock: os.Lock.Spin = .init("e1000.rx");
var rx_ring: [16]Desc.Rx align(16) = @splat(.{});
var rx_bufs: [16]*Page = undefined;

pub fn init(base: usize) !void {
    reg = .init(base);

    // Reset the device
    reg.write(.ims, 0x0); // disable interrupts
    reg.set(.ctl, .{ .rst = true });
    reg.write(.ims, 0x0); // redisable interrupts
    fence();

    // [E1000 14.5] Transmit initialization
    const tx_ring_size = @sizeOf(@TypeOf(tx_ring));
    comptime assert(tx_ring_size % 128 == 0);
    reg.writeInt(.tdbal, 0, @intFromPtr(&tx_ring));
    reg.write(.tdlen, tx_ring_size);
    reg.write(.tdh, 0);
    reg.write(.tdt, 0);

    // [E1000 14.4] Receive initialization
    for (&rx_bufs, &rx_ring) |*buf, *ring| {
        buf.* = try allocator.create(Page);
        ring.addr = @intFromPtr(buf.*);
    }

    const rx_ring_size = @sizeOf(@TypeOf(rx_ring));
    comptime assert(rx_ring_size % 128 == 0);
    reg.writeInt(.rdbal, 0, @intFromPtr(&rx_ring));
    reg.write(.rdlen, rx_ring_size);
    reg.write(.rdh, 0);
    reg.write(.rdt, rx_ring.len - 1);

    // filter by qemu's MAC address, 52:54:00:12:34:56
    const ra: *align(4) volatile e1000.Ra = @ptrCast(reg.array(.ra));
    ra.* = .{
        .mac = std.mem.bytesToValue(u48, &os.net.local_mac),
        .av = true,
    };

    // multicast table
    const mta = reg.array(.mta);
    for (mta[0 .. 4096 / 32]) |*r| {
        r.* = 0;
    }

    // transmitter control bits.
    reg.set(.tctl, .{
        .en = true, // enable
        .psp = true, // pad short packets
        .ct = 0x10, // collision stuff
        .cold = 0x40,
    });
    reg.write(.tipg, .{ .ipgt = 10, .ipgr1 = 8, .ipgr2 = 6 }); // inter-pkt gap

    // receiver control bits.
    reg.set(.rctl, .{
        .en = true, // enable receiver
        .bam = true, // enable broadcast
        .bsize = .@"256", // 256 * 16 byte rx buffers
        .bsex = true, // bsize * 16
        .secrc = true, // strip CRC
    });

    // ask e1000 for receive interrupts.
    reg.write(.rdtr, 0); // interrupt after every received packet (no timer)
    reg.write(.radv, 0); // interrupt after every packet (no timer)
    reg.write(.ims, 1 << 7); // RXDW -- Receiver Descriptor Write Back
}

// buf contains an ethernet frame
pub fn transmit(buf: []u8) !void {
    // Acquire lock to prevent concurrent access
    tx_lock.acquire();
    defer tx_lock.release();

    // Get current transmit descriptor index
    const tail = reg.read(.tdt);

    // Check if transmit ring is full by checking DD (Descriptor Done) bit
    if (!tx_ring[tail].status.dd) {
        return error.SystemResources;
    }

    // Free last transmitted buffer if exists
    if (tx_bufs[tail]) |bytes| {
        allocator.free(bytes);
    }

    // Store buffer pointer for later freeing
    tx_bufs[tail] = buf;

    // Setup transmit descriptor
    tx_ring[tail].addr = @intFromPtr(buf.ptr);
    tx_ring[tail].length = @intCast(buf.len);

    // Set necessary command flags:
    // EOP - End of Packet
    // RS - Report Status
    tx_ring[tail].cmd = .{ .eop = true, .rs = true };

    // Clear status
    tx_ring[tail].status = .{};

    // Update tail pointer
    reg.write(.tdt, @intCast((tail + 1) % tx_ring.len));
}

pub fn intr() void {
    // tell the e1000 we've seen this interrupt; without this the e1000 won't raise any further
    // interrupts.
    reg.write(.icr, 0xffff_ffff);
    recv() catch @panic("e1000 recv");
}

/// Check for packets that have arrived from the e1000. Create and deliver a buf for each packet.
fn recv() !void {
    // The e1000 can deliver more than one packet per interrupt
    while (true) {
        // Acquire lock to prevent concurrent access
        rx_lock.acquire();
        defer rx_lock.release();

        // Get next descriptor to check
        const tail = (reg.read(.rdt) + 1) % rx_ring.len;

        // Check if new packet is available
        if (!rx_ring[tail].status.dd) break;

        // If packet has no errors
        if (!rx_ring[tail].status.eop) return error.PacketIncomplete;

        // Get the received packet length
        const length = rx_ring[tail].length;

        // Deliver packet to network stack
        try os.net.recv(rx_bufs[tail][0..length]);

        // Clear the descriptor's status bits to zero.
        rx_ring[tail].status = .{};

        // Update tail register to mark this packet as processed
        reg.write(.rdt, @intCast(tail));
    }
}

/// Registers
pub const e1000 = enum(usize) {
    /// Device Control Register - RW
    ctl = 0x00000,
    /// Interrupt Cause Read - R
    icr = 0x000C0,
    /// Interrupt Mask Set - RW
    ims = 0x000D0,
    /// RX Control - RW
    rctl = 0x00100,
    /// TX Control - RW
    tctl = 0x00400,
    /// TX Inter-packet gap -RW
    tipg = 0x00410,
    /// RX Descriptor Base Address Low - RW
    rdbal = 0x02800,
    /// RX Delay Timer
    rdtr = 0x02820,
    /// RX Interrupt Absolute Delay Timer
    radv = 0x0282C,
    /// RX Descriptor Head - RW
    rdh = 0x02810,
    /// RX Descriptor Tail - RW
    rdt = 0x02818,
    /// RX Descriptor Length - RW
    rdlen = 0x02808,
    /// RX Small Packet Detect Interrupt
    rsrpd = 0x02C00,
    /// TX Descriptor Base Address Low - RW
    tdbal = 0x03800,
    /// TX Descriptor Length - RW
    tdlen = 0x03808,
    /// TX Descriptor Head - RW
    tdh = 0x03810,
    /// TX Descriptor Tail - RW
    tdt = 0x03818,
    /// Multicast Table Array - RW Array
    mta = 0x05200,
    /// Receive Address - RW Array
    ra = 0x05400,

    pub const Int = u32;

    // [E1000 13.5.2]
    /// Receive Address
    pub const Ra = packed struct(u64) {
        mac: u48,
        as: u2 = 0,
        @"18-30": u13 = 0,
        /// Address Valid
        av: bool,
    };

    /// Device Control Register
    pub const Ctl = packed struct(Int) {
        /// Full-Duplex
        fd: bool = false,
        @"1-2": u2 = 0,
        /// Link Reset
        lrst: bool = false,
        @"4": u1 = 0,
        /// Auto-Speed Detection Enable
        asde: bool = false,
        /// Set Link Up
        slu: bool = false,
        /// Invert Loss-of-Signal (LOS)
        ilos: bool = false,
        /// Speed selection
        speed: enum(u2) { @"10Mbps", @"100Mbps", @"1000Mbps", not_used } = .@"10Mbps",
        @"10": u1 = 0,
        /// Force Speed
        frcspd: bool = false,
        /// Force Duplex
        frcdplx: bool = false,
        @"13-17": u5 = 0,
        sdp0_data: u1 = 0,
        sdp1_data: u1 = 0,
        /// D3Cold Wakeup Capability Advertisement Enable
        advd3wuc: bool = false,
        /// PHY Power-Management Enable
        en_phy_pwr_mgmt: bool = false,
        sdp0_iodir: bool = false,
        sdp1_iodir: bool = false,
        @"24-25": u2 = 0,
        /// Device Reset
        rst: bool = false,
        /// Receive Flow Control Enable
        rfce: bool = false,
        /// Transmit Flow Control Enable
        tfce: bool = false,
        @"29": u1 = 0,
        /// VLAN Mode Enable
        vme: bool = false,
        /// PHY Reset
        phy_rst: bool = false,
    };

    /// Transmit IPG Register
    pub const Tipg = packed struct(Int) {
        /// IPG Transmit Time
        ipgt: u10,
        /// IPG Receive Time 1
        ipgr1: u10,
        /// IPG Receive Time 2
        ipgr2: u10,
        _: u2 = 0,
    };

    /// Transmit Control Register
    pub const Tctl = packed struct(Int) {
        @"0": u1 = 0,
        /// Transmit Enable
        en: bool = false,
        @"2": u1 = 0,
        /// Pad Short Packets
        psp: bool = false,
        /// Collision Threshold
        ct: u8 = 0,
        /// Collision Distance
        cold: u10 = 9,
        /// Software XOFF Transmission
        swxoff: bool = false,
        @"23": u1 = 0,
        /// Re-transmit on Late Collision
        rtlc: bool = false,
        /// No Re-transmit on underrun (82544GC/EI only)
        nrtu: bool = false,
        @"26-31": u6 = 0,
    };

    /// Receive Control Register
    pub const Rctl = packed struct(Int) {
        @"0": u1 = 0,
        /// Receive Enable
        en: bool = false,
        /// Store Bad Packets
        sbp: bool = false,
        /// Unicast Promiscuous Enabled
        upe: bool = false,
        /// Multicast Promiscuous Enabled
        mpe: bool = false,
        /// Long Packet Reception Enable
        lpe: bool = false,
        /// Loopback mode.
        lbm: enum(u2) { no, undefined1, undefined2, serdes } = .no,
        /// Receive Descriptor Minimum Threshold Size
        rdmts: enum(u2) { @"1/2", @"1/4", @"1/8", reserved } = .@"1/2",
        @"10-11": u2 = 0,
        /// Multicast Offset
        mo: enum(u2) { @"bits[47:36]", @"bits[46:35]", @"bits[45:34]", @"bits[43:32]" } = .@"bits[47:36]",
        @"14": u1 = 0,
        /// Broadcast Accept Mode
        bam: bool = false,
        /// Receive Buffer Size
        bsize: enum(u2) { @"2048", @"1024", @"512", @"256" },
        /// VLAN Filter Enable
        vfe: bool = false,
        /// Canonical Form Indicator Enable
        cfien: bool = false,
        /// Canonical Form Indicator bit value
        cfi: u1 = 0,
        @"21": u1 = 0,
        /// Discard Pause Frames
        dpf: bool = false,
        /// Pass MAC Control Frames
        pmcf: bool = false,
        @"24": u1 = 0,
        /// Buffer Size Extension
        /// When set to one, the original bsize values are multiplied by 16
        bsex: bool = false,
        /// Strip Ethernet CRC from incoming packet
        secrc: bool = false,
        @"27-31": u5 = 0,
    };
};

pub const Desc = struct {
    // [E1000 3.3.3]
    /// Transmit Descriptor - Legacy Mode
    pub const Tx = packed struct(u128) {
        addr: u64 = 0,
        length: u16 = 0,
        cso: u8 = 0,
        cmd: Cmd = .{},
        status: Status,
        rsv: u4 = 0,
        css: u8 = 0,
        special: u16 = 0,

        pub const Cmd = packed struct(u8) {
            /// End Of Packet
            eop: bool = false,
            /// Insert FCS
            ifcs: bool = false,
            /// Insert Checksum
            ic: bool = false,
            /// Report Status
            rs: bool = false,
            /// Report Packet Sent
            rps: bool = false,
            /// Extension (0 for legacy mode)
            dext: bool = false,
            /// VLAN Packet Enable
            vle: bool = false,
            /// Interrupt Delay Enable
            ide: bool = false,
        };

        pub const Status = packed struct(u4) {
            /// Descriptor Done
            dd: bool = false,
            /// Excess Collisions
            ec: bool = false,
            /// Late Collision
            lc: bool = false,
            /// Transmit Underrun
            tu: bool = false,
        };
    };

    // [E1000 3.2.3]
    /// Receive Descriptor
    pub const Rx = packed struct(u128) {
        addr: u64 = 0,
        length: u16 = 0,
        csum: u16 = 0,
        status: Status = .{},
        errors: u8 = 0,
        special: u16 = 0,

        pub const Status = packed struct(u8) {
            /// Descriptor Done
            dd: bool = false,
            /// End of Packet
            eop: bool = false,
            /// Ignore Checksum Indication
            ixsm: bool = false,
            /// Packet is 802.1Q (matched VET)
            vp: bool = false,
            /// Reserved
            rsv: bool = false,
            /// TCP Checksum Calculated on Packet
            tcpcs: bool = false,
            /// IP Checksum Calculated on Packet
            ipcs: bool = false,
            /// Passed in-exact filter
            pif: bool = false,
        };
    };
};
